#pragma once

#include "alloc.h"

namespace sfc::vec {

template <class T, class A = alloc::Global>
struct RawVec {
  static inline auto _A = A();

  T* _ptr;
  usize _cap;

  explicit RawVec() noexcept : _ptr{nullptr}, _cap{0} {}

  explicit RawVec(T* ptr, usize capacity) noexcept : _ptr{ptr}, _cap{capacity} {}

  RawVec(RawVec&& other) noexcept : _ptr{mem::take(other._ptr)}, _cap{mem::take(other._cap)} {}

  ~RawVec() {
    _A.dealloc_array(_ptr, _cap);
  }

  static auto with_capacity(usize capacity) -> RawVec {
    const auto p = _A.template alloc_array<T>(capacity);
    return RawVec{p, capacity};
  }
};

template <class T, class A = alloc::Global>
struct Vec {
  using Buf = RawVec<T>;

  Buf _buf;
  usize _len;

  [[sfc_inline]] Vec() noexcept : _buf{}, _len{0} {}

  [[sfc_inline]] explicit Vec(Buf&& buf, usize len) noexcept : _buf{mem::move(buf)}, _len{len} {}

  [[sfc_inline]] Vec(Vec&& other) noexcept
      : _buf{mem::move(other._buf)}, _len{mem::take(other._len)} {}

  ~Vec() {
    this->clear();
  }

  static auto with_capacity(usize capacity) -> Vec {
    return Vec{Buf::with_capacity(capacity), 0};
  }

  [[sfc_inline]] auto capacity() const {
    return _buf._cap;
  }

  [[sfc_inline]] auto ptr() -> T* {
    return _buf._ptr;
  }

  [[sfc_inline]] auto len() const -> usize {
    return _len;
  }

  [[sfc_inline]] auto as_slice() const -> Slice<const T> {
    return {_buf._ptr, _len};
  }

  [[sfc_inline]] auto as_slice_mut() -> Slice<T> {
    return {_buf._ptr, _len};
  }

  [[sfc_inline]] operator Slice<const T>() const->Slice<const T> {
    return {_buf._ptr, _len};
  }

  [[sfc_inline]] operator Slice<T>()->Slice<const T> {
    return {_buf._ptr, _len};
  }

  [[sfc_inline]] auto operator[](usize idx) -> T& {
    return this->as_slice_mut()[idx];
  }

  [[sfc_inline]] auto operator[](usize idx) -> T& {
    return this->as_slice()[idx];
  }

  void clear() {
    this->truncate(0);
  }

  void truncate(usize new_len) {
    if (new_len >= _len) {
      return;
    }
    for (auto idx = _len; idx < new_len; ++idx) {
      mem::drop(_ptr[idx]);
    }
    this->_len = new_len;
  }

  void reserve(usize additional) {
    if (this->_len + additional < this->_cap) {
      return;
    }
    const auto new_cap = cmp::max(this->_cap * 2, this->_len + additional);
    const auto old_vec = mem::replace(*this, Vec::with_capacity(new_cap));
    this->_len = old_vec._len;
    ptr::move(old_vec._ptr, this->_ptr, this->_len);
  }

  void push(T value) {
    this->reserve(1);
    auto dst = this->_ptr + this->_len;
    new (dst) T{static_cast<T&&>(value)};
    this->_len += 1;
  }

  auto pop() -> Option<T> {
    if (this->_len == 0) {
      return {};
    }
    auto& res = _ptr[this->_len - 1];
    this->_len -= 1;
    return mem::move(res);
  }

  void extend(Slice<const T> src) {
    this->reserve(src._len);
    ptr::copy(src._ptr, _ptr + _len, src._len);
    this->_len += src._len;
  }
};

}  // namespace sfc::vec
